/* 
 * 	$Id: DumbPlayer.java,v 1.3 2007/11/14 22:04:58 johnc Exp $
 * 
 * 	Programming and Problem Solving
 *  Copyright (c) 2007 The Trustees of Columbia University
 */

package explorandum.g3;

import java.awt.Color;
import java.awt.Point; //import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;

//import explorandum.GameConstants;
import explorandum.Logger;
import explorandum.Move;
import explorandum.Player;

public class Group3Player implements Player {
	Logger log;
	Random rand;
	static int lastmove; // last valid move
	static ArrayList<Point> walked = new ArrayList<Point>(); 
	// Arraylist which maintains all walked positions
	static int prevx, prevy; // previous x and y according to our coordinate system
	static Map world;
	Integer[] TerAndExp;
	static boolean following;
	static int timesfollowed;
	static int offsetx,	offsety;

	int roundsNo;
	int explorersNo;
	int rangeNo;
	int ID;

	public void register(int explorerID, int rounds, int explorers, int range,
			Logger _log, Random _rand) {
		// TODO Auto-generated method stub
		log = _log;
		rand = _rand;

		ID = explorerID;
		roundsNo = rounds; // n hours
		explorersNo = explorers; // p groups
		rangeNo = range; // d distance

		//world = new Map(ID);
		//walked.clear();
		//walked.add(new Point(0, 0));
		world = new Map(ID);
		following = false;
		timesfollowed = 0;
		offsetx = 0;
		offsety = 0;

	}

	public Color color() throws Exception {
		return Color.ORANGE;
	}

	// return a list of all valid actions (excluding stayput,
	// but inclusive of cells we have already traversed)
	public ArrayList<Integer> getValid(Point cur) {
		ArrayList<Integer> val = new ArrayList<Integer>();
		// skip i=0 == STAYPUT
		for(int i=1; i< ACTIONS.length; i++) {
			int cell = world.getCell(cur.x+_dx[i],cur.y+_dy[i]);
			if (cell != WATER && cell != MOUNTAIN) {
				val.add(i);
			}
		}
		return val;
	}
	
	public ArrayList<Integer> getValid(Point cur, Point[] offsets, Integer[] terr) {
		ArrayList<Integer> val = new ArrayList<Integer>();

		// Loop through adjacent cells
		for (int i = 0; i < _dx.length; i++) {
			if (_dx[i] != 0 || _dy[i] != 0) {
				// Find this cell in visible cells list
				for (int j = 0; j < offsets.length; j++) {
					Point point = offsets[j];

					// if adjacent cell and LAND
					if (point.x == cur.x + _dx[i] && point.y == cur.y + _dy[i]
							&& terr[j] == LAND) {
						// add to valid actions list
						// (i is the index of the action needed to move to this
						// cell)
						val.add(i);
						break;
					}
				}
			}
		}
		return val;
	}

	// TODO: do this in terms of map, not offsets+terrain
	// return true if loc is adjacent to a water cell
	public boolean nearWater(Point[] off, Integer[] ter, int a) {
		for (int i = 0; i < off.length; i++) {
			if (ter[i] == WATER) {
				return ((off[i].x + 1 == _dx[a] || off[i].x - 1 == _dx[a]) && (off[i].y + 1 == _dy[a] || off[i].y - 1 == _dy[a]));
			}
		}
		return false;
	}

	// equality also counts as adjacency
	public boolean adjacent(Point p1, Point p2) {
		return ((p1.x == p2.x || p1.x == p2.x + 1 || p1.x == p2.x - 1) && (p1.y == p2.y
				|| p1.y == p2.y + 1 || p1.y == p2.y - 1));
	}

	// get subset of valid directions that are near water.
	public ArrayList<Integer> getNearWater(ArrayList<Integer> val, Point cur) {
		ArrayList<Integer> pref = new ArrayList<Integer>();
		for (int i = 0; i < val.size(); i++) {
			int act = val.get(i);
			if (world.adjacentFeature(new Point(cur.x + _dx[act], cur.y
					+ _dy[act]), WATER)) {
				pref.add(act);
			}
		}
		return pref;
	}
	
	public ArrayList<Integer> AdjacentToFeature(Point cur) {
		ArrayList<Integer> pref = new ArrayList<Integer>();
		for (int i = 1; i < 9; i++) {
			if (world.adjacentFeature(new Point(cur.x + _dx[i], cur.y + _dy[i]), WATER)) {
				pref.add(i);
			}
			else if (world.adjacentFeature(new Point(cur.x + _dx[i], cur.y + _dy[i]), MOUNTAIN)) {
				pref.add(i);
			}
		}
		return pref;
	}

	// Return true if two points are diagonally adjacent to one another
	public boolean diagonal(Point cur, Point offset) {
		int dx = Math.abs(cur.x-offset.x);
		int dy = Math.abs(cur.y-offset.y);
		return dx == 1 && dy == 1;
	}

	// Return true if two points are next to one another but not diagonal
	public boolean nextto(Point cur, Point offset) {
		int dx = Math.abs(cur.x-offset.x);
		int dy = Math.abs(cur.y-offset.y);
		return ( adjacent(cur,offset)  && 
				 !(dx == 1 && dy == 1) &&
				 !(dx == 0 && dy == 0) );
	}
	
	// of valid moves, which ones put us on a cell that is orthogonally
	// adjacent to mountains or water
	public ArrayList<Integer> getNearWaterOrMountain(Point cur, ArrayList<Integer> val) {
		ArrayList<Integer> pref = new ArrayList<Integer>();
		int[] orthActs = {NORTH,EAST,SOUTH,WEST};
		
		for (int i=0; i< val.size(); i++) { 
			// coord corresponding to act=val[i]
			Point act = new Point(cur.x+_dx[val.get(i)],cur.y+_dy[val.get(i)]);
			// for each orthogonal action
			for (int dir : orthActs) {
				int cell = world.getCell(act.x+_dx[dir],act.y+_dy[dir]); // value of cell in DIR from ACT;
				if (cell == WATER || cell == MOUNTAIN || cell == Map.UNCLAIMABLE_LAND) {
					pref.add(val.get(i));
				}
			}
		}
		return pref;
	}
	
	public ArrayList<Integer> getNearWaterOrMountain(Point cur,
			ArrayList<Integer> val, Point[] off, Integer[] ter) {

		ArrayList<Integer> pref = new ArrayList<Integer>();

		for (int i = 0; i < val.size(); i++) {

			Point offset = new Point(_dx[val.get(i)] + cur.x, _dy[val.get(i)]
					+ cur.y);

			for (int j = 0; j < ter.length; j++) { // for each of the offsets
				if ((ter[j] == WATER || ter[j] == MOUNTAIN)
						&& adjacent(off[j], offset)) { // if it is water and
					// adjacent to the action offset
					pref.add(val.get(i));
					break;
				}
			}
		}

		ArrayList<Integer> feature = new ArrayList<Integer>();

		// Get list of points in visible area with features on it
		for (int j = 0; j < ter.length; j++) {
			if (ter[j] == WATER || ter[j] == MOUNTAIN) {
				feature.add(j);
			}
		}

		ArrayList<Integer> diag = new ArrayList<Integer>();

		// If the feature is diagonal but not nextto to the preferred cells
		// remove those cells
		for (int i = 0; i < pref.size(); i++) {
			Point newpref = new Point(_dx[pref.get(i)] + cur.x,
					_dy[pref.get(i)] + cur.y);

			for (int j = 0; j < feature.size(); j++)
				// for each of the features
				if (diagonal(off[feature.get(j)], newpref))
					diag.add(pref.get(i));
		}

		for (int i = 0; i < diag.size(); i++) {
			int noadj = 0;
			Point newpref = new Point(_dx[diag.get(i)] + cur.x,
					_dy[diag.get(i)] + cur.y);

			for (int j = 0; j < feature.size(); j++)
				if (nextto(off[feature.get(j)], newpref))
					noadj += 1;

			if (noadj == 0)
				pref.remove(diag.get(i));
		}

		return pref;
	}

	// remove any acts that put us on squares we have already walked on
	public ArrayList<Integer> prunePref(ArrayList<Integer> pref) {
		ArrayList<Integer> arr = new ArrayList<Integer>();
		for (int i = 0; i < pref.size(); i++) {
			Point p = new Point(PickX(pref.get(i)), PickY(pref.get(i)));
			if (world.getCell(p) != Map.OUR_LAND) {
				arr.add(pref.get(i));
			}
		}
		return arr;
	}

	private void PopulatePlayerSits(Integer[][] otherExplorers,
			Integer[] terrain) {
		TerAndExp = new Integer[500];
		for (int i = 0; i < terrain.length; i++)
			TerAndExp[i] = terrain[i];

		for (int i = 0; i < otherExplorers.length; i++)
			if (otherExplorers[i][0] != null)
				if (otherExplorers[i][0] == ID)
					TerAndExp[i] = 4;
				else
					TerAndExp[i] = 5;
	}

	public Move move(Point cur, Point[] offsets, Boolean[] hasExplorer,
			Integer[][] otherExplorers, Integer[] terrain, int time)
			throws Exception {

		world.updateMap(cur, offsets, hasExplorer, otherExplorers, terrain);
		//System.out.println("(14,3)="+world.getCell(14,3));
		//System.out.println("(14,-3)="+world.getCell(14,-3));
		// build a list of valid locations
		//ArrayList<Integer> valid = getValid(cur, offsets, terrain);
		ArrayList<Integer> valid = getValid(cur);
		ArrayList<Integer> preferred = getNearWaterOrMountain(cur, valid,
				offsets, terrain);
		preferred = prunePref(preferred);

		PopulatePlayerSits(otherExplorers, terrain);

		// if no valid location, stay put
		if (valid.size() == 0) {
			lastmove = -2;
			log.debug("Found no valid moves");
			return new Move(STAYPUT);
		}

		int x = 0, y = 0;

		boolean newcell = true;
		int whiletries = 0;

		// Deadlock breaking hack
		while (newcell) {
			whiletries += 1;
			if (whiletries > 8) {
				//System.out.println("DEADLOCK DETECTED");
				return new Move(valid.get(rand.nextInt(valid.size())));
			}

			// 1. IF the last move was not -2 i.e. STAYPUT
			// 1. and our valid locations array does not contain the last move
			// 1. (i.e. we cannot go forward in the same direction) OR
			// 2. IF all the locations surrounding the player have already been
			// traversed
			if ((lastmove != -2 && valid.contains(lastmove) == false)
					|| ( (world.getCell(x,y) == Map.OUR_LAND) && (x!=0 || y!=0) )) {
				// If there is a mountain or water to follow,
				if (preferred.size() != 0) {
					// preferred is populated with coast following directions
					// So we choose one of those directions
					lastmove = preferred.get(rand.nextInt(preferred.size()));
					// // For Debugging
					// System.out.print("Preferred: ");
					// for (int i = 0; i < preferred.size(); i++)
					//System.out.print(GameConstants.ACTION_NAMES[preferred.get(
					// i)] + " ");
					// System.out.println();
				}
				// We are in an unknown area with untraversed cells around us
				// and no features to follow
				else {
					// THEN pick a random move and assign it to lastmove.
					lastmove = valid.get(rand.nextInt(valid.size()));
					//System.out.println("Valid");
				}

			}
			// If our previous direction is valid but we see a feature in
			// another direction
			else {
				if (preferred.size() != 0) {
					// Then pick a direction at random from preferred
					lastmove = preferred.get(rand.nextInt(preferred.size()));
					// // For Debugging
					// System.out.print("Preferred2: ");
					// for (int i = 0; i < preferred.size(); i++)
					//System.out.print(GameConstants.ACTION_NAMES[preferred.get(
					// i)] + " ");
					// System.out.println();
				}
			}
			
			// If we see another player
			if (OtherPlayerVisible(offsets)) {
				//System.out.println("Other player visible");
				// If he is moving in the same direction and in front of us
				if (OtherPlayerSameDirection(cur, lastmove, offsets)) {
					//System.out.println("Other player same dir");
					following = true;
				}
			} else {
				following = false;
				timesfollowed = 0;
			}

			// Change preferred dirs to match your x or y
			// Find out if coast is to N,S,E or W and walk one step to opp.
			// dir
			// If no coast we are following player in open area
			// In that case we pick random N,S,E or W and then keep
			// adding offsets to lastmove
			// Now add preferred dirs to walked so that you won't
			// traverse the traversed path again
			// Add x or y to pref acc. to the dir you picked to move inland
			// Now choose from this new pref
			
			// If we have found a player we are following
			if (following) {
//				System.out.println("Following: " + following);
//				int bx = world.basicX;
//				int by = world.basicY;
//				for(int i=0;i<preferred.size(); i++)
//				{
//					int px = cur.x + _dx[preferred.get(i)];
//					int py = cur.y + _dy[preferred.get(i)];
//					Map.map[px + bx][py + by] = Map.UNCLAIMABLE_LAND;
//				}
					
				
//				timesfollowed += 1;
//				System.out.println("Following: " + following);
//				System.out.println("Lastmove: " + lastmove);
//				System.out.println("timesfollowed: " + timesfollowed);
//				
//				// If we have found a pattern of following
//				if (timesfollowed > 2) {
//					ArrayList<Integer> ppp = AdjacentToFeature(cur); 
//					if (ppp.size() > 0 && offsetx == 0 && offsety == 0) {
//						System.out.println("Adjacent to feature");
//						// Move away from coast or mountain
//						AddPreferredtoWalked(preferred);
//						for(int i=0; i<ppp.size(); i++)
//						{
//							Point newpt = offsets[ppp.get(i)];
//							//System.out.println("Cur: " + cur.x + " " + cur.y);
//							//System.out.println("Water: " + newpt.x + " " + newpt.y);
//							
//							if(cur.x - 1 == newpt.x && cur.y == newpt.y)
//							{
//								System.out.println("Going north or south");
//								lastmove = 3;
//								offsetx = 1;
//								offsety = 0;
//							}
//							else if(cur.x + 1 == newpt.x && cur.y == newpt.y)
//							{
//								System.out.println("Going north or south");
//								lastmove = 7;
//								offsetx = -1;
//								offsety = 0;
//							}
//							else if(cur.x == newpt.x && cur.y - 1 == newpt.y)
//							{
//								System.out.println("Going east or west");
//								lastmove = 4;
//								offsetx = 0;
//								offsety = 1;
//							}
//							else if(cur.x == newpt.x && cur.y + 1 == newpt.y)
//							{
//								System.out.println("Going east or west");
//								lastmove = 1;
//								offsetx = 0;
//								offsety = -1;
//							}
//						}
//					}
//					else
//					{
//						//traverse coast with one cell difference
//						if(offsetx != 0 || offsety != 0)
//						{
//							System.out.println("In offset mode");
//							ArrayList<Integer> newpref = new ArrayList<Integer>();
//							ArrayList<Point> newpts = new ArrayList<Point>();
//							
//							for(int i=0; i<preferred.size(); i++)
//							{
//								newpts.add(new Point
//										(cur.x + _dx[preferred.get(i)] + offsetx,
//										 cur.y + _dy[preferred.get(i)] + offsety));
//							}
//							
//							System.out.println("New Dirs: ");
//							for(int i=0; i<newpts.size(); i++)
//							{
//								newpref.add(GetDir(cur, newpts.get(i)));
//								System.out.println(GetDir(cur, newpts.get(i)));
//							}
//							
//							lastmove = newpref.get(rand.nextInt(preferred.size()));
//						}			
//					}
//				}
			}

			// Calculation of x & y coordinates according to our system
			x = PickX(lastmove);
			y = PickY(lastmove);

			// If we figured out a location which has not been traversed pick
			// that and exit the while loop
			//if (walked.contains(new Point(x, y)) == false || walked.size() == 0)
			if ( world.getCell(x,y) != Map.OUR_LAND ) // || time == 0;
				newcell = false;
			else
			// The location we picked has been traversed
			{
				int allcells = 0;

				// This is the calculation to check if all the surrounding valid
				// cells have been traversed
				for (int i = 0; i < valid.size(); i++) {
					//if (Traversed(walked, valid.get(i)))
					if ( world.getCell(cur.x+_dx[valid.get(i)],cur.y+_dy[valid.get(i)]) == Map.OUR_LAND )
						allcells += 1;
				}

				// If all surrounding valid cells have been traversed then
				// "right now"
				// we pick a random next move
				// TODO: THIS COULD BE WHERE WE DECIDE BASED ON OPEN AREAS IN
				// OUR WORLD REPRESENTATION
				if (allcells == valid.size()) {
					//System.out.println("Surrounding Cells Traversed");
					lastmove = valid.get(rand.nextInt(valid.size()));
					newcell = false;
				}
			}
		}

		prevx = x;
		prevy = y;

		// If calculated cell is not already a part of the walked array add it.
		//if (walked.contains(new Point(x, y)) == false)
		//	walked.add(new Point(x, y));

		return new Move(lastmove);
	}

	private Integer GetDir(Point c, Point p) {
		if(c.x - p.x == 0)
		{
			if(c.y - p.y == 1)
				return NORTH;
			else if(c.y - p.y == -1)
				return SOUTH;
		}
		else if(c.y - p.y == 0)
		{
			if(c.x - p.x == 1)
				return WEST;
			else if(c.x - p.x == -1)
				return EAST;
		}
			return 0;
	}

	private void AddPreferredToWater(ArrayList<Integer> preferred) {
		for(int i=0; i<preferred.size(); i++)
		{
			walked.add(new Point(PickX(lastmove) + _dx[preferred.get(i)], 
					PickY(lastmove) + _dy[preferred.get(i)]));	
		}
	}

	// Checks if there is another player in our range of vision, d
	private boolean OtherPlayerVisible(Point[] offsets) {
		boolean ret = false;
		for (int i = 0; i < offsets.length; i++) {
			if (TerAndExp[i] == 5) {
				ret = true;
				break;
			}
		}
		return ret;
	}

	// Checks if another player is moving in the same direction and in front of us
	private boolean OtherPlayerSameDirection(Point cur, int lastmove,
			Point[] offsets) {
		// Logic determined using position diagrams

		Point otherplayer = null;
		int odx = 0, ody = 0;

		for (int i = 0; i < offsets.length; i++) {
			if (TerAndExp[i] == 5) {
				otherplayer = offsets[i];
				break;
			}
		}
		
		if (otherplayer != null) {
			odx = cur.x - otherplayer.x;
			ody = cur.y - otherplayer.y;

			if (odx == 0) {
				if (ody < 0) {
					if (lastmove == 4)
						return true;
					else
						return false;
				} else {
					if (lastmove == 1)
						return true;
					else
						return false;
				}
			} else if (ody == 0) {
				if (odx < 0) {
					if (lastmove == 3)
						return true;
					else
						return false;

				} else {
					if (lastmove == 7)
						return true;
					else
						return false;
				}
			} else
				return false;
		} else
			return false;

	}

	// Checking if cell has already been traversed
	private boolean Traversed(ArrayList<Point> walked, int valid) {
		if (walked.contains(new Point(PickX(valid), PickY(valid))))
			return true;
		else
			return false;
	}

	// Calculating x according to our coordinate system
	private int PickX(int lastmove) {
		return prevx + _dx[lastmove];
	}

	// Calculating y according to our coordinate system
	private int PickY(int lastmove) {
		return prevy + _dy[lastmove];
	}

	public String name() throws Exception {
		return "CIA Ladies";
	}
}
